package calworker

import (
	"bytes"
	"context"
	"cv-rpc/models"
	"errors"
	"fmt"
	"log"
	"os/exec"
	"strings"
	"syscall"
	"time"

	amqp "github.com/rabbitmq/amqp091-go"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Panicf("%s: %s", msg, err)
	}
}

func CalWork() {
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "Failed to connect to RabbitMQ")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	q, err := ch.QueueDeclare(
		"rpc_queue", // name
		false,       // durable
		false,       // delete when unused
		false,       // exclusive
		false,       // no-wait
		nil,         // arguments
	)
	failOnError(err, "Failed to declare a queue")

	err = ch.Qos(
		1,     // prefetch count
		0,     // prefetch size
		false, // global
	)
	failOnError(err, "Failed to set QoS")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		false,  // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)
	failOnError(err, "Failed to register a consumer")

	var forever chan struct{}

	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		// 获取mongodb client
		client := models.GetMongoClient()
		defer client.Disconnect(context.TODO())

		for d := range msgs {
			log.Printf("receive the message", string(d.Body))
			log.Println("receive the ReplyTo", d.ReplyTo)
			log.Println("receive the CorrelationId", d.CorrelationId)
			respJson, err := processImageFromMongo(client, d.CorrelationId)
			if err != nil {
				failOnError(err, "SaveImageFromMongo")
				break
			}
			log.Printf("Sending response JSON: %s", string(respJson))
			err = ch.PublishWithContext(ctx,
				"",        // exchange
				d.ReplyTo, // routing key
				false,     // mandatory
				false,     // immediate
				amqp.Publishing{
					ContentType:   "application/json",
					CorrelationId: d.CorrelationId,
					Body:          []byte(respJson),
				})
			failOnError(err, "Failed to publish a message")

			d.Ack(false)
		}
	}()

	log.Printf(" [*] Awaiting RPC requests")
	<-forever
}

// 根据管道内insertId获取图片流, 传入c++工具中
// 此方法为一个耗时操作
func processImageFromMongo(client *mongo.Client, insertID string) (resp string, err error) {
	log.Println("save images with insertID", insertID)
	// 获取pictures collection
	collection := client.Database("testdb").Collection("pictures")
	objID, err := primitive.ObjectIDFromHex(insertID)
	if err != nil {
		return "", err
	}

	filter := bson.D{{"_id", objID}}
	var result models.Picture
	err = collection.FindOne(context.TODO(), filter).Decode(&result)
	if err != nil {
		return "", err
	}

	log.Println("MongoDB result content: \n", result.FileName, result.ID.Hex())

	return callCppProgram(result.Content, result.StartX, result.StartY, result.Width, result.Height)
}

// 调用c++程序
func callCppProgram(imageData []byte, startX, startY, width, height int) (string, error) {
	// 启动 C++ 程序
	cmd := exec.Command("cmd/demo")

	// 设置 stdin 和 stdout
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return "", fmt.Errorf("failed to open stdin pipe: %v", err)
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return "", fmt.Errorf("failed to open stdout pipe: %v", err)
	}

	// 启动 C++ 程序
	if err := cmd.Start(); err != nil {
		return "", fmt.Errorf("failed to start C++ program: %v", err)
	}

	// 将参数写入 stdin
	_, err = fmt.Fprintf(stdin, "%d %d %d %d\n", startX, startY, width, height)
	if err != nil {
		return "", fmt.Errorf("failed to write parameters to stdin: %v", err)
	}

	// 将图片数据写入 C++ 程序的 stdin
	_, err = stdin.Write(imageData)
	if err != nil {
		return "", fmt.Errorf("failed to write to stdin: %v", err)
	}
	stdin.Close()

	// 读取 C++ 程序的输出
	var output bytes.Buffer
	_, err = output.ReadFrom(stdout)
	if err != nil {
		return "", fmt.Errorf("failed to read from stdout: %v", err)
	}

	// 等待 C++ 程序完成
	if err = cmd.Wait(); err != nil {
		var exitErr *exec.ExitError
		if errors.As(err, &exitErr) {
			// 提取退出状态码
			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
				fmt.Printf("C++ program exited with status: %d", status.ExitStatus())
			}
		}
		return "", fmt.Errorf("C++ program failed: %v", err)
	}
	// 提取 JSON 数据
	rawOutput := output.String()
	startMarker := "---JSON-BEGIN---"
	endMarker := "---JSON-END---"
	startIndex := strings.Index(rawOutput, startMarker)
	endIndex := strings.Index(rawOutput, endMarker)

	jsonData := rawOutput[startIndex+len(startMarker) : endIndex]

	// 打印提取的 JSON 数据
	fmt.Println("Extracted JSON data:")
	fmt.Println(jsonData)
	return jsonData, nil
}
